package de.jmda.core.mproc;

import static de.jmda.core.util.FileUtil.toSourceFilePath;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;

import org.junit.Test;

import de.jmda.core.MarkerAnnotationType;
import de.jmda.core.mproc.LaunchConfiguration;
import de.jmda.core.mproc.Launcher;
import de.jmda.core.mproc.Processor;

public class JUTProcessorUnusualImplementations extends AbstractJUTProcessors
{
	@MarkerAnnotationType private class TestTypeProcessed { }

	/**
	 * Regular Processor implementation (nothing missing)
	 */
	@SupportedAnnotationTypes(value = { "de.jmda.core.MarkerAnnotationType" })
	@SupportedSourceVersion(value = SourceVersion.RELEASE_7)
	private class TestProcessor extends Processor
	{
		@Override
		protected boolean process() { return false; }

		@Override
		protected Set<Class<? extends Annotation>> getSupposedSupportedAnnotationTypes()
		{
			return asSet(MarkerAnnotationType.class);
		}
	}
	
	/**
	 * Processor implementation without any {@link SupportedAnnotationTypes}
	 * annotation
	 */
	@SupportedSourceVersion(value = SourceVersion.RELEASE_7)
	private class TestProcessorSupportedAnnotationTypesMissing extends Processor
	{
		@Override
		protected boolean process() { return false; }

		@Override
		protected Set<Class<? extends Annotation>> getSupposedSupportedAnnotationTypes()
		{
			return asSet(MarkerAnnotationType.class);
		}
	}
	
	/**
	 * Processor implementation with <code>null</code> return value from {@link
	 * #getSupportedAnnotationTypes()}
	 */
	@SupportedAnnotationTypes(value = { "de.jmda.core.MarkerAnnotationType" })
	@SupportedSourceVersion(value = SourceVersion.RELEASE_7)
	private class TestProcessorSupposedSupportedAnnotationTypesNull extends Processor
	{
		@Override
		protected boolean process() { return false; }
	
		@Override
		protected Set<Class<? extends Annotation>> getSupposedSupportedAnnotationTypes()
		{
			return null;
		}
	}

	/**
	 * Processor implementation with empty but non <code>null</code> return value
	 * from {@link #getSupportedAnnotationTypes()}
	 */
	@SupportedAnnotationTypes(value = { "de.jmda.core.MarkerAnnotationType" })
	@SupportedSourceVersion(value = SourceVersion.RELEASE_7)
	private class TestProcessorSupposedSupportedAnnotationTypesEmpty extends Processor
	{
		@Override
		protected boolean process() { return false; }
	
		@Override
		protected Set<Class<? extends Annotation>> getSupposedSupportedAnnotationTypes()
		{
			return new HashSet<>();
		}
	}

	/**
	 * empty implementation to bypass setup process in super class
	 * @see de.jmda.core.mproc.AbstractJUTProcessors#setUp()
	 */
	@Override
  public void setUp() throws Exception { }

	/**
	 * returns empty list, is not used because setup process in super class is
	 * bypassed
	 * @see de.jmda.core.mproc.AbstractJUTProcessors#getProcessors()
	 */
	@Override
	protected List<? extends Processor> getProcessors()
	{
		return new ArrayList<>();
	}

	/**
	 * tests behaviour for missing {@link SupportedAnnotationTypes} annotation
	 * (expects {@link ExceptionInInitializerError})
	 */
	@Test(expected = ExceptionInInitializerError.class)
	public void testSupportedAnnotationMissing() throws Throwable
	{
		LaunchConfiguration launchConfiguration = new LaunchConfiguration();
		launchConfiguration.files.addAll(getJavaFilesWithSupportedAnnotations());
		launchConfiguration.processors.add(new TestProcessorSupportedAnnotationTypesMissing());

		try
		{
			Launcher.launch(launchConfiguration);
		}
		catch (RuntimeException e)
		{
			throw e.getCause();
		}
	}

	/**
	 * tests behaviour for <code>null</code> return value in {@link
	 * Processor#getSupposedSupportedAnnotationTypes()} (expects {@link
	 * NullPointerException})
	 */
	@Test(expected = NullPointerException.class)
	public void testSupposedSupportedAnnotationNull() throws Throwable
	{
		LaunchConfiguration launchConfiguration = new LaunchConfiguration();
		launchConfiguration.files.addAll(getJavaFilesWithSupportedAnnotations());
		launchConfiguration.processors.add(new TestProcessorSupposedSupportedAnnotationTypesNull());

		try
		{
			Launcher.launch(launchConfiguration);			
		}
		catch (RuntimeException e)
		{
			throw e.getCause();
		}
	}

	/**
	 * tests behaviour for <code>null</code> return value in {@link
	 * Processor#getSupposedSupportedAnnotationTypes()} (expects warning message
	 * in log)
	 */
	@Test
	public void testSupposedSupportedAnnotationEmpty() throws IOException
	{
		LaunchConfiguration launchConfiguration = new LaunchConfiguration();
		launchConfiguration.files.addAll(getJavaFilesWithSupportedAnnotations());
		launchConfiguration.processors.add(new TestProcessorSupposedSupportedAnnotationTypesEmpty());
		
		Launcher.launch(launchConfiguration);
	}

	@Test
	public void testFindSourceFile()
	{
		String sourceFilePath = toSourceFilePath(JUTProcessorUnusualImplementations.class);

		File sourceFile = findFileInFilesToProcess(sourceFilePath);

		assertTrue(
				"couldn't find " + sourceFilePath + " in files to process",
				sourceFile != null);
	}

	@Test
	public void testExistsSourceFile()
	{
		String sourceFilePath = toSourceFilePath(JUTProcessorUnusualImplementations.class);

		File sourceFile = findFileInFilesToProcess(sourceFilePath);

		assertTrue(sourceFilePath + " does not exist", sourceFile.exists());
	}

	private File findFileInFilesToProcess(String sourceFilePath)
	{
		for (File file : Processor.FILES_TO_PROCESS)
		{
			if (file.getAbsolutePath()
			        .replaceAll("\\\\", "\\" + File.separator)
			        .endsWith(sourceFilePath))
			{
				return file;
			}
		}
		
		return null;
	}
}